#!/usr/bin/python
# -*- coding: utf-8 -*-

### Imports ###
#import datetime
#import urllib
#import thread
import ConfigParser
import twitter
import re,os,sys
import xmpp
import time
import saUtils
import sqlite3
try:
  import oauth.oauth as oauth
except ImportError:
  import oauth

import oauthtwitter

__author__  = "Sun]["
__version__ = "0.3"
### INIT section ###
#global config_name, max_tw_len, DEBUG
config_name	= 'sa-twiter-bot.conf'
max_tw_len	= 140
DEBUG		= 1
#Consumer key
consumer_key = 'tl3QSHByx9KhoyxbmekDIA'
#Consumer secret
consumer_secret = 'fNDKt7HkaIzwdodVFguIGiLr0w60EljNI0EZjxmnF0'


### CHDIR to directory of Script ###
os.chdir(os.path.dirname(__file__))

### Get config ###
#global configdict
configdict = ConfigParser.ConfigParser()
#global tw_username, tw_password, last_tid, jabber_jid, jabber_password, jabber_resource, owner_jid
error_count = 0
last_tid = 0
last_dm = 0
tw_username = ''
log = None
try:
  configdict.read(config_name)
  tw_key          = configdict.defaults()['tw_key']
  tw_secret       = configdict.defaults()['tw_secret']

  jabber_jid		  = configdict.defaults()['jabber_jid']
  jabber_password	= configdict.defaults()['jabber_password']
  jabber_resource	= configdict.defaults()['jabber_resource']

  owner_jid		    = configdict.defaults()['owner_jid']
  last_entry_file	= configdict.defaults()['last_entry_file']

  t_style_user		= configdict.defaults()['t_style_user']
  t_style_login		= configdict.defaults()['t_style_login']
  t_style_tag		  = configdict.defaults()['t_style_tag']
  t_style_command	= configdict.defaults()['t_style_command']
  t_style_link		= configdict.defaults()['t_style_link']
  t_style_quote   = configdict.defaults()['t_style_quote']
  sqlite_db_file  = configdict.defaults()['sqlite_db_file']

  DEBUG           = int(configdict.defaults()['debug'])
  log_file        = configdict.defaults()['log_file']
  daemon          = int(configdict.defaults()['daemon'])
except:
  print "Error in configuration file"
  quit(1)

if (tw_key == '') or (tw_secret == ''):
  tw = oauthtwitter.OAuthApi(consumer_key, consumer_secret)
  token = tw.getRequestToken()
  url   = tw.getAuthorizationURL(token)
  print "Authorise this application on \n%s" % url
  pin = raw_input('Enter PIN: ')
  pin = int(pin)
  try:
    tw = oauthtwitter.OAuthApi(consumer_key, consumer_secret,token)
    #token = tw.getAccessToken(pin)
    token = tw.getAccessToken(pin)
  except Exception as e:
    print "Error during authorization: \n"
    print e
    quit(5)
  configdict.set('DEFAULT','tw_key',token.key)
  tw_key = token.key
  configdict.set('DEFAULT','tw_secret',token.secret)
  tw_secret = token.secret
  try:
    config_file=open(config_name,'w+')
    configdict.write(config_file)
    config_file.close()
  except:
    print "Error saving config file."
    quit(4)

try:
  f = open(last_entry_file,'r')
  last_tid = int(f.readline())
  last_dm = int(f.readline())
  f.close()
except:
  last_tid = 0
  last_dm = 0

sqldb = sqlite3.connect(sqlite_db_file)
sqlcur = sqldb.cursor()
sqlcur.execute('CREATE TABLE IF NOT EXISTS hist (id PRIMARY KEY, text)')
sqlcur.execute('VACUUM')

### Functions ###
def line():
  try:
    raise Exception
  except:
    return sys.exc_info()[2].tb_frame.f_back.f_lineno

def save_last_entry():
  """Save new last_tid value"""
  try:
    f = open(last_entry_file,'w')
    f.write('%d\n%d' % (last_tid, last_dm))
    f.close()
  except:
    print "Error saving config file."
#    quit(4)
def writeLog(text):
  '''Write message to log'''
  timenow = time.localtime(time.time())
  stime   = time.strftime("%b %d %H:%M:%S", timenow)
  log.write(''.join(['[',stime,'] ',xmpp.ustr(text).encode('utf-8'),'\n']))
#  print ''.join(['[',stime,'] ',str(text),'\n'])

def addTwit2DB(twitt):
  sqlcur.execute('INSERT OR REPLACE INTO hist VALUES(?,?)',(twitt.id, twitt.text))
  sqldb.commit()

def getTwitMsg(id):
  sqlcur.execute('SELECT text FROM hist where id = '+str(id))
  row = sqlcur.fetchone()
  if row is None:
    status = tw.GetStatus(id)
    if status is None:
      return ''
    addTwit2DB(status)
    return status.text
  else:
    return row[0]

def sendError(e,line=0):
  """Send error to Bot Owner"""
  saJBot.send(xmpp.Message(owner_jid,''.join([str(e),' in line #', str(line)]),'chat'))

#Twitter check
def check_twitter():
  """Check twitter home timeline and DM for new messages"""
  global last_tid, last_dm
  ### Home timeline ###
  new_le = last_tid
  try:
    if new_le == 0:
      statuses = tw.GetHomeTimeline(count=10)
    else:
      statuses = tw.GetHomeTimeline(since_id=last_tid)
  except Exception as e:
    #sendError(repr(e),line())
    #sendError(e,line())
    if DEBUG:
      writeLog('Twitter Check Error:')
      writeLog(e) 
    return False
  for msg in statuses[::-1]:
    if msg.id > new_le: new_le = msg.id
    SendTwitt(msg)
    time.sleep(1)

  ## DM
  new_ldm = last_dm
  try:
    try:
      dms = tw.GetDirectMessages(since_id=last_dm)
    except Exception as e:
      #sendError(e,line())
      if DEBUG:
        writeLog('DM Check Error:')
        writeLog(e)
      raise
    for dm in dms[::-1]:
      if dm.id > new_ldm: new_ldm = dm.id
      SendDM(dm)
      time.sleep(1)
  except:
    pass

  if (new_le != last_tid) or (new_ldm != last_dm):
    last_tid = new_le
    last_dm  = new_ldm
    save_last_entry()
  return True

def stripTags(str):
  '''Delete HTML-tags from text'''
  return re.sub('</?\s*\w+.*?>','',str)

def stripTags2(str):
  '''Delete HTML-tags from text'''
  return re.sub('</?\s*\w+.*?>','',re.sub('(^|[^\n])<br\s*?/>([^\n]|$)','\\1\n\\2',str))

def convTags(str):
  '''Convert HTML-tags to'''
  return str.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')

def saDeHTML(str):
  """confert HTML special simbols to normal"""
  list = {'gt':'>', 'lt':'<', 'mdash':u'—', 'quot':'"', \
      'amp':'&'}
  for a in list:
    str = str.replace(''.join(['&',a,';']),list[a])
  return str

def addLink(parent, href, text = None, style = None):
  '''Add link 2 XMPP Message'''
  if text is None:
    text = href
  attrs = {'href': href}
  if style is not None:
    attrs['style'] = style
  
  node = parent.addChild(name='a', attrs=attrs)
  node.addData(text)
  return node

def addXMPPMessLink(parent, mess, text = None):
  '''Add Command 2 Bot as Link'''
  if text is None:
    text = mess
  return addLink(parent, \
      'xmpp:%s?message;type=chat;body=%s' % (jabber_jid, mess.replace('#','%23')), \
      text, t_style_command )

def makeMessage(to):
  '''Make XMPP Message'''
  msg = xmpp.Message(to=to,typ='chat')
  body = msg.addChild(name='html',namespace='http://jabber.org/protocol/xhtml-im',).addChild(name='body', namespace='http://www.w3.org/1999/xhtml')
  return (msg, body)

def AddPlainPart2Message(msg, body):
  '''Add plain\text part to XMPP message, which have HTML part'''
  msg.setTagData('body',saDeHTML(stripTags2(xmpp.ustr(body))))

def parseAddMsg(parent,message):
  """Parse message for getting links and etc..."""

  lines = saDeHTML(message).split('\n')
  for a in lines:
    ## Try to parse links and users
    while 1:
      alen = len(a)
      s1 = alen+1
      s2 = s1
      s3 = s1
      m1 = re.search('(^|[^\d\w_])@([\d\w_]+)([^\d\w_]|\s|$)',a) # user
      if m1 is not None:  s1 = m1.start()+len(m1.group(1))
      m2 = re.search('(^|\s)(\w+://\S+)(\s|$)',a) # some link
      if m2 is not None:  s2 = m2.start()+len(m2.group(1))
      m3 = re.search('(^|\s)#(\S+)(\s|$)',a) # tag
      if m3 is not None:  s3 = m3.start()+len(m3.group(1))

      ## Something found
      if (s1 < alen) or (s2 < alen) or (s3 < alen):
        if (s1 < s2) and (s1 < s3): # USER
          a1=a[:s1]
      	  s2 = m1.end() - len(m1.group(3))
      	  a2=a[s1:s2]
      	  a3=a[s2:]
#     	  link = 'http://twitter.com/'+a2[1:]
          link = 'xmpp:%s?message;type=chat;body=@%s' % (jabber_jid, a2[1:])
          style = t_style_login
        elif (s2 < s1) and (s2 < s3): # LINK
          a1=a[:s2]
          s1 = m2.end() - len(m2.group(3))
          a2=a[s2:s1]
          a3=a[s1:]
          link = a2
          style = t_style_link
        else: # Tag
          a1=a[:s3]
          s1 = m3.end() - len(m3.group(3))
          a2=a[s3:s1]
          a3=a[s1:]
          link = 'http://twitter.com/#search?q=%23'+a2[1:]
          style = t_style_tag

        parent.addData(a1)
        addLink(parent, link, a2, style)
        a = a3
      else: 
        break
    ## End of while

    parent.addData(a)
    parent.addChild(name='br')

  #end of for a in lines
###########################

def SendDM(dm):
  """Send dm message to jabber"""
#  addDM2DB(dm) # -_-
  (msg, body) = makeMessage(owner_jid)
  body.addChild(name='br')
  body.addData('Direct Message from ')
  addLink(body, 'http://twitter.com/%s' % (dm.sender_screen_name), ''.join(['@',dm.sender_screen_name]), t_style_user)
  body.addData(':')
  body.addChild(name='br')

  parseAddMsg(body,dm.text)
  body.addChild(name='br')

  addXMPPMessLink(body, '#dm %s' % (dm.sender_screen_name), 'Reply')
  body.addData(' ')
  addXMPPMessLink(body, ''.join(['#DDM ', str(dm.id)]),'D')
  body.addData(' ')

  AddPlainPart2Message(msg,body)

  saJBot.send(msg)

def SendTwitt(twitt):
  """Send twitt message to jabber"""
  addTwit2DB(twitt)
  (msg, body) = makeMessage(owner_jid)
#  body.setData(twitt.user.name)
  body.addChild(name='br')
  addLink(body, 'http://twitter.com/%s' % \
    (twitt.user.screen_name), twitt.user.name, t_style_user)
  if twitt.in_reply_to_status_id is not None:
    body.addData(' in reply to ')
    addXMPPMessLink(body,''.join(['#',str(twitt.in_reply_to_status_id)]))
    
  body.addData(':')
  body.addChild(name='br')
  if twitt.in_reply_to_status_id is not None:
    try:
      body.addChild(name='span', attrs={'style': t_style_quote}).addData(''.join(['>',getTwitMsg(twitt.in_reply_to_status_id)]))
      body.addChild(name='br')
    except Exception as e:
      sendError(e,line())

#  body.addChild(name='img',attrs={'src':twitt.user.profile_image_url, 'align':'top'})
#  TODO At this moment remote Pictures doesn't work (in PSI+)
#  body.addChild(name='br')

  parseAddMsg(body,twitt.text)
  body.addChild(name='br')

  addXMPPMessLink(body, '#%d @%s' % (twitt.id, twitt.user.screen_name), \
      ''.join(['#',str(twitt.id)]))
  body.addData(' ')
  if twitt.user.screen_name == tw_username:
    addXMPPMessLink(body, ''.join(['#D ', str(twitt.id)]),'D')
  else:
    addXMPPMessLink(body, '#rt %d' % (twitt.id), '!')
  body.addData(' ')
  addLink(parent=body, href='http://twitter.com/%s/status/%d' % (twitt.user.screen_name, twitt.id),\
      style=t_style_link)
  AddPlainPart2Message(msg,body)
  saJBot.send(msg)

def presenceCB(sess,pres):
  """Handler for incoming presences"""
  # @type sess xmpp.dispatcher.Dispatcher
  # @typ pres xmpp.protocol.Presence
  if DEBUG:
    writeLog(''.join(["New unknown presence: [",xmpp.ustr(pres).encode('utf-8'),"]"]))
  return True

def iqCB(sess, iq):
  """Handler for incoming IQ's"""
  # @type sess xmpp.dispatcher.Dispatcher
  # @type iq xmpp.protocol.Iq
  if iq.getType() == "get":
    if iq.getQueryNS() == "jabber:iq:version":
      newIQ = iq.buildReply('result')
      newIQ.setQueryPayload([xmpp.simplexml.Node(tag='name',payload=['sai-jTwitt']), \
                             xmpp.simplexml.Node(tag='version',payload=[__version__]), \
                             xmpp.simplexml.Node(tag='os',payload=['Unknown OS =)'])])
      #TODO determine OS
      sess.send(newIQ)
      return True

  if DEBUG:
    writeLog(''.join(["New unknown iq: [",str(iq).encode('utf-8'),"]"]))
  return True

def messageCB(sess,mess):
  """Handler for incoming messages"""
  # @type sess xmpp.dispatcher.Dispatcher
  # @type mess xmpp.protocol.Message
  if mess.getBody() is None: 
    if DEBUG:
      writeLog('Got empty message from %s' % (mess.getFrom().getStripped()))
    return True

  if (mess.getFrom().getStripped() == owner_jid ): # Only messages from owner are interesting
    if DEBUG:
      writeLog("message from Owner:\n" + mess.getBody().encode('utf-8'))
      writeLog("RAW message: ")
      writeLog(''.join(["New unknown presence: [",xmpp.ustr(mess).encode('utf-8'),"]"]))
      writeLog('===========================')
    body = mess.getBody()
    cmd = re.search('^#(\S*)(\s+.*$|$)',body,re.S)
    if cmd is None: # there is not command, just send message to Twitter
      (msg,mbody) = makeMessage(owner_jid)
      mbody.setData('You send: ')
      mbody.addChild(name='br')
      mbody.addData(body)
      AddPlainPart2Message(msg,mbody)
      sess.send(msg)
      return True
      """try:
        status = tw.PostUpdate(body)
      except Exception as e:
        sendError(e,line())
        return False
      (msg,body) = makeMessage(owner_jid)
      body.setData('Status successfully updated: ')
      body.addChild(name='br')
      addXMPPMessLink(body, ''.join(['#D ', str(status.id)]),'D')
      body.addData(' ')
      addXMPPMessLink(body, ''.join(['#', str(status.id)]))
      body.addData(' ')
      addLink(parent = body, href = 'http://twitter.com/%s/status/%d' % (tw_username, status.id), \
          style=t_style_link)
      AddPlainPart2Message(msg,body)
      sess.send(msg)
      return True"""
    else: # It's command =)
      command = cmd.group(1).lower()
      args = cmd.group(2).strip()
#      print "[%s][%s]\n" % (command, args)
      isid = True
      try:
        id = int(command)
      except:
        isid = False
      if isid: #It's reply or something like that
        if args != '': # It's reply =)
          try:
            status = tw.PostUpdate(args,command)
          except Exception as e:
            sendError(e,line())
            return False
          (msg,body) = makeMessage(owner_jid)
          body.setData('Status successfully updated: ')
      	  body.addChild(name='br')
          addXMPPMessLink(body, ''.join(['#D ', str(status.id)]),'D')
          body.addData(' ')
          addXMPPMessLink(body, ''.join(['#', str(status.id)]))
          body.addData(' ')
          addLink(parent = body, href = 'http://twitter.com/%s/status/%d' % (tw_username, status.id), \
              style=t_style_link)
          AddPlainPart2Message(msg,body)
          sess.send(msg)
          return True
        else: # It's command for fetching message
          try:
            status = tw.GetStatus(command)
            if status is None:
              sess.send(xmpp.Message(owner_jid,"Message #%d doesn't exists" % (command),'chat'))
            else:
              SendTwitt(status)
            return True
          except Exception as e:
            sendError(e,line())
      elif (command == 'say'): # say something
        try:
          status = tw.PostUpdate(args)
        except Exception as e:
          sendError(e,line())
          return False
        (msg,body) = makeMessage(owner_jid)
        body.setData('Status successfully updated: ')
        body.addChild(name='br')
        addXMPPMessLink(body, ''.join(['#D ', str(status.id)]),'D')
        body.addData(' ')
        addXMPPMessLink(body, ''.join(['#', str(status.id)]))
        body.addData(' ')
        addLink(parent = body, href = 'http://twitter.com/%s/status/%d' % (tw_username, status.id), \
            style=t_style_link)
        AddPlainPart2Message(msg,body)
        sess.send(msg)
        return True

      elif (command == 'rt'): # Do retweet
        try:
          try: 
            args = int(args)
          except:
            sess.send(xmpp.Message(to=owner_jid,typ='chat',body='Use: #rt status_id'))
            return False
          tw.RetweetPost(args)
          sess.send(xmpp.Message(owner_jid,"Message #%d successfully retweeted" % (args),'chat'))
        except Exception as E:
          if DEBUG: writeLog("Some error: " + str(E))
          sendError(E, line())

      elif (command == 'ddm'): # Delete DM
        try:
          try: 
            args = int(args)
          except:
            sess.send(xmpp.Message(to=owner_jid,typ='chat',body='Use: #ddm dm_id'))
            return False
          tw.DestroyDirectMessage(args)
          sess.send(xmpp.Message(owner_jid,"Direct Message #%d successfully deleted" % (args),'chat'))
        except Exception as E:
          if DEBUG: writeLog("Some error: " + str(E))
          sendError(E,line())
      elif (command == 'dm'): #send DM
        m = re.match('^(\S+)\s+(\S.*)$',args)
        if m is None:
          sess.send(xmpp.Message(to=owner_jid,typ='chat',body='Use: #dm user text'))
        user = m.group(1)
        text = m.group(2)
        try:
          dm = tw.PostDirectMessage(user,text)
        except Exception as e:
          sendError(e,line())
          return False
        (msg,body) = makeMessage(owner_jid)
        body.setData('Direct message for ')
        addLink(parent = body, href = 'http://twitter.com/%s' % (user), text = ''.join(['@',user]))
        body.addData(' sucessfully sent')
        body.addChild(name='br')
        addXMPPMessLink(body, ''.join(['#DDM ', str(dm.id)]),'D')
        AddPlainPart2Message(msg,body)
        sess.send(msg)
        return True

      elif (re.match('#+$',command)):
        page = len(command)
        try:
          statuses = tw.GetHomeTimeline(count=10,page=page)
        except Exception as e:
          sendError(e,line())
          if DEBUG:
            writeLog('Error: '+str(e))
          #raise
          return False
        for msg in statuses[::-1]:
#          if DEBUG:
#            print msg.id,' ',msg.in_reply_to_status_id,' ',msg.text.encode('UTF-8'),"\n"
          SendTwitt(msg)
          time.sleep(1)
      elif (command == 'd'):
        try:
          try: 
            args = int(args)
          except:
            sess.send(xmpp.Message(to=owner_jid,typ='chat',body='Use: #d status_id'))
            return False
          tw.DestroyStatus(args)
          sess.send(xmpp.Message(owner_jid,"Message #%d successfully deleted" % (args),'chat'))
        except Exception as E:
          sendError(E,line())
          print "Some error: ",E,"\n"

      elif (command == 'help'):
        sess.send(xmpp.Message(owner_jid,u'''Бот для общения с Twitter через Jabber
Все команды начинаются с #
#номер_сообщения - показать сообщение
#номер_сообщение @автор текст - ответить
#rt номер_сообщения - сделать ретвит
#d номер_сообщения - удалить свое сообщение
## - посмотреть последние 10 сообщений (### - 2я страница и так далее)
#help - справка
#dm юзер текс - послать личное сообщение
#ddm номер_личного_сообщения - удалить личное сообщение
''','chat'))
      else: # Another commands
	#TODO not implemented yet
        sess.send(xmpp.Message(owner_jid,"Unknown command: %s" % (command),'chat'))
        print "Unknown command: ", command, "\n"
        return True
   
  return True

### Main program, begin
log = open(log_file, 'a')
if daemon:
  saUtils.createDaemon()

# Init Twitter client
tw = oauthtwitter.OAuthApi(consumer_key, consumer_secret, oauth.OAuthToken(tw_key,tw_secret))
tw.base_url = 'https://api.twitter.com/1'
#tw = twitter.Api(username=tw_username, password=tw_password)
tw.SetSource('sa-jTwit')
#tw.SetUrllib('http://juick.com/SunX')
tw.SetXTwitterHeaders('sa-jTwit','http://code.google.com/p/sa-jtwitter-bot/',__version__)
time.sleep(1)

tw_username = tw.GetUserInfo().screen_name


# Init Jabber client
saJBot = xmpp.Client(xmpp.JID(jabber_jid).getDomain(),debug=[])
saJBot.connect()
###
saJBot.RegisterHandler('message',messageCB)
saJBot.RegisterHandler('presence',presenceCB)
saJBot.RegisterHandler('iq',iqCB)
###
saJBot.auth(xmpp.JID(jabber_jid).getNode(),jabber_password,jabber_resource)

saJBot.sendInitPresence(0)

twitTime = 0
## Main cycle =)
while 1:
  saJBot.Process(1)
  cur_time = time.time()
  log.flush()
  if (cur_time - twitTime) >= 60:
    check_twitter()
    twitTime = time.time()
  time.sleep(0.25)

log.close()
